Element.extend({
	empty: function() {
		while (this.lastChild) this.removeChild(this.lastChild);
	},
	show: function(show) {
		this.style.display = show ? 'block' : 'none';
	}
});

var MooTreeIcon = {I:252,L:234,Lminus:216,Lplus:198,T:180,Tminus:162,Tplus:144,_closed:126,_doc:108,_open:90,minus:72,plus:54,Rminus:36,Rplus:18};

var MooTreeControl = new Class({
	
	initialize: function(options) {
		options.control = this;               // make sure our new MooTreeNode knows who it's owner control is
		this.root = new MooTreeNode(options); // create the root node of this tree control
		this.selected = null;                 // set the currently selected node to nothing
		this.mode = options.mode;             // mode can be "folders" or "files", and affects the default icons
		this.grid = options.grid;             // grid can be turned on (true) or off (false)
	},
	
	insert: function(options) {
		/* inserts a new node under the root node */
		options.control = this;
		return this.root.insert(options);
	},
	
	update: function() {
		/* paint the tree control */
		this.root.update(true);
	},
	
	select: function(node) {
		/* sets the currently selected node - this is called by MooTreeNode when selected */
		if (this.selected) this.selected.select(false);
		node.select(true);
		this.selected = node;
	},
	
	expand: function() {
		/* expands the entire tree, recursively */
		this.root.toggle(true, true);
	},

	collapse: function() {
		/* collapses the entire tree, recursively */
		this.root.toggle(true, false);
	}
	
});

var MooTreeNode = new Class({
	
	initialize: function(options) {
		
		this.text = options.text;       // the text displayed by this node
		this.nodes = new Array();       // subnodes nested beneath this node (MooTreeNode objects)
		this.parent = null;             // this node's parent node (another MooTreeNode object)
		this.last = true;               // a flag telling whether this node is the last (bottom) node of it's parent
		this.control = options.control; // owner control of this node's tree
		this.selected = false;          // a flag telling whether this node is the currently selected node in it's tree
		
		this.open = options.open ? true : false; // flag: node open or closed?
		
		this.icon = options.icon;
		this.openicon = options.openicon ? options.openicon : this.icon;
		
		// create the necessary divs:
		this.div = {
			main: new Element('div').addClass('mooTree_node'),
			indent: new Element('div').addClass('mooTree_indent'),
			gadget: new Element('div').addClass('mooTree_gadget'),
			icon: new Element('div').addClass('mooTree_icon'),
			text: new Element('div').addClass('mooTree_text'),
			sub: new Element('div').addClass('mooTree_sub')
		}
		
		// put the main div in the specified parent div:
		$(options.div).adopt(this.div.main);
		
		// put the other divs under the main div:
		this.div.main.adopt(this.div.indent);
		this.div.main.adopt(this.div.gadget);
		this.div.main.adopt(this.div.icon);
		this.div.main.adopt(this.div.text);
		this.div.main.adopt(this.div.sub);
		
		// attach event handler to gadget:
		this.div.gadget._node = this;
		this.div.gadget.onclick = this.div.gadget.ondblclick = function() {
			this._node.toggle();
		}
		
		// attach event handler to icon/text:
		this.div.icon._node = this.div.text._node = this;
		this.div.icon.onclick = this.div.icon.ondblclick = this.div.text.onclick = this.div.text.ondblclick = function() {
			this._node.control.select(this._node);
		}
		
	},
	
	insert: function(options) {
		
		/* creates and returns a new MooTreeNode, nested inside this one */
		
		// set the parent div and create the node:
		options.div = this.div.sub;
		var node = new MooTreeNode(options);
		
		// set the new node's parent and control:
		node.parent = this;
		node.control = node.parent.control;
		
		// mark this node's last node as no longer being the last, then add the new last node:
		var n = this.nodes;
		n.length ? n[n.length-1].last = false : null;
		n.push(node);
		
		// repaint the parent node:
		if (n.length == 1) this.update();
		
		// repaint the previous node:
		if (n.length > 1) n[n.length-2].update();
		
		return node;
		
	},
	
	remove: function() {
		
		/* removes this node, and all of it's child nodes */
		
		// recursively remove this node's subnodes:
		this.nodes.forEach(function(node) {
			node.remove();
		});
		
		if (this.parent) {
			// remove this node from the parent's collection of nodes:
			var p = this.parent.nodes;
			p.remove(this);
			// in case we removed the parent's last node, flag it's current last node as being the last:
			p.length ? p[p.length-1].last = true : null;
		}
		
		// remove this node's div:
		this.div.main.remove();
		
	},
	
	update: function(recursive) {
		
		/* update the tree node's visual appearance */
		
		var x;
		
		// make selected, or not:
		var x = this.div.text;
		this.selected ? x.addClass('mooTree_selected') : x.removeClass('mooTree_selected');
		
		// update indentations:
		x = this.div.indent;
		x.empty();
		var p = this, i;
		while (p.parent) {
			p = p.parent;
			i = this.getImg(p.last || !this.control.grid ? '' : 'I');
			if (x.firstChild) {
				i.injectBefore( x.firstChild );
			} else {
				x.adopt(i);
			}
		}
		
		// update the text:
		x = this.div.text;
		x.empty();
		x.appendText(this.text);
		
		// update the icon:
		x = this.div.icon;
		x.empty();
		if (this.control.mode == 'folders') {
			x.adopt( this.getImg( this.nodes.length && this.open ? (this.openicon || '_open') : (this.icon || '_closed') ) );
		} else {
			x.adopt( this.getImg( this.nodes.length ? (this.openicon || (this.open ? '_open' : '_closed')) : (this.icon || '_doc') ) );
		}
		
		// update the plus/minus gadget:
		x = this.div.gadget;
		x.empty();
		x.adopt( this.getImg( ( this.control.grid ? ( this.control.root == this ? 'R' : (this.last?'L':'T') ) : '') + (this.nodes.length ? (this.open?'minus':'plus') : '') ) );
		
		// show/hide subnodes:
		this.div.sub.show(this.open);
		
		// if recursively updating, update all child nodes:
		if (recursive) this.nodes.forEach( function(node) {
			node.update(true);
		});
		
	},
	
	getImg: function(name) {
		
		/* create and return a new image (a div element) */
		
		if (name == '') return new Element('div').addClass('mooTree_blank');
		
		var x = MooTreeIcon[name];
		if (x) {
			var e = new Element('div').addClass('mooTree_img');
			e.style.backgroundPosition = x + 'px 0px';
			return e;
		}
		window.alert('Error - missing image: ' + name);
		
	},
	
	toggle: function(recursive, state) {
		
		/*
		
		By default (with no arguments) this function toggles the current node between expanded/collapsed.
		
		With recursive set to true, it recursively toggles all child nodes to it's own new state, too.
		
		With the state argument set to true/false, the node can be explicitly opened or closed.
		
		*/
		
		this.open = (state === undefined ? !this.open : state);
		this.update();
		if (recursive) this.nodes.forEach( function(node) {
			node.toggle(true, this.open);
		}, this);
		
	},
	
	select: function(state) {
		
		/* called by MooTreeControl when selection changes */
		
		this.selected = state;
		this.update();
		
	}
	
});
